from functools import cached_property
from pydantic import BaseModel
from .map import getNet, getInfoMap
import re
from .model import getModel


class LinkPoint(BaseModel):
    component: 'Component'
    point: int|str
    
    class Config:
        arbitrary_types_allowed = True
        
class Node(BaseModel):
    name: str
    symbol: str = ''
    link_points: list[LinkPoint] = []
    
    class Config:
        arbitrary_types_allowed = True

    def __str__(self):
        # 接地节点统一使用0
        return  self.symbol
    
    @property
    def is_valid(self):
        return not (self.symbol.startswith(('0')) or self.name.startswith(('-','+')))
    
class Info(BaseModel):
    value: str = ''
    part_name:str = ''

# 解析获取对应的元器件, 包含映射参数、设置备注、对应是否第三方
class Component:
    def __init__(self, name:str, info: Info):
        self.name = name
        self.point:dict[str|int, Node] = {} 
        self.p_mark:dict[str|int, str] = {}
        self.nc_points:list[str|int] = []
        self.info=info
        self._data:list = None # 用于记录加载组
        self.waring = []
        self.error = []
        self._is_link_complate = False # 是否构建完成
        
    def link_complate(self):
        self._is_link_complate = True
        self._data = getModel(self.name, self.class_, self.info.value, self.ignore, self.pin_num)        
        if self._data and len(self._data)==3:
            self.waring.append(self._data[2])
        if self.class_ == 'R' and self.value=='1e-3':
            self.waring.append(f'电阻-{self.name} 视为导线, 阻值为1e-3欧姆')
                    
    def __str__(self):
        remark = f'* {self.remark}\n' if self.remark else ""
        # 每个悬空点单独设置
        intp,strp=[],[]
        for p in list(self.point.keys())+self.nc_points:
            if isinstance(p, int): intp.append(p)
            else: strp.append(p)
        intp,strp = sorted(intp),sorted(strp)   
        point = " ".join(str(self.point.get(i, f'NC{i}{self.name}')) for i in intp+strp)
        return f'{remark}{self.symbol} {point} {self.value}'
    
    @cached_property
    def class_(self)->str:
        return re.match(r'([A-Z]+)[+-_0-9]+', self.name).group(1)
    
    # 判断是否为已处理元器件
    @cached_property
    def is_norm(self):
        return self.class_ in ('C','D','E','FB','R','V','TP')
    
    @cached_property
    def symbol(self)->str:
        name = ("" if self.is_norm else "X") + self.name.replace('+','P').replace('-','M')
        if name.startswith(('TP','FB')): name='R'+name
        return name
    
    @cached_property
    def pin_num(self)->int:
        assert self._is_link_complate
        return len(self.point)+len(self.nc_points)
    
    # 视为空子电路
    @cached_property
    def ignore(self)->str:
        assert self._is_link_complate
        return not self.is_norm and not (self.class_=='U' and self.pin_num in (3,5,8))
        
    @cached_property
    def model(self)->str:
        assert self._is_link_complate
        if self._data: return self._data[1]
        return None
                
    @cached_property
    def value(self)->str:
        assert self._is_link_complate
        if self.class_ == 'TP': return '1e-3'
        if self.class_ == 'FB': return '1e-3'
        # 判断为电源, | 优先级最低
        if self.class_ == 'V' and (rer:=re.match(r'((\+|\-)\d+)V', self.info.value)):
            # 默认都为直流
            return f'DC {float(rer.group(1))}'
        elif self.class_ == 'R':
            rer = re.search(r"([0-9.a-zA-Z-]+?ohm)/*", self.info.value)
            if rer: rer = rer.group(1).replace('-','') 
            if not rer or rer.startswith('0'): return '1e-3'
            return rer
        elif self.class_ == 'C':
            rer = re.match(r"([0-9.a-zA-Z-]+)/*", self.info.value)
            return rer.group(1).replace('-','') 
        if self._data: return self._data[0]
        return ''

    @cached_property
    def remark(self)->str:
        assert self._is_link_complate
        if self.class_ == 'TP': return '测试点视为1毫欧电阻'
        if self.class_ == 'FB': return '磁珠视为1毫欧电阻'
        if self.ignore: return '设置为多pin空的子电路'
        remark = '' if re.match(r"\+|-", self.info.value) else f'{self.info.part_name}; {self.info.value}'
        return remark
    
    # 元器件自身校验
    def verify(self):
        if not self.value:
            self.error.append(f'{self.name} 没有解析到value, 元器件参数: {self.info.value}')
        # 悬空检测
        if self.nc_points and not self.ignore:
            if self.name.startswith(('C','R','FB','TP','D')) or self.symbol.startswith('V'):
                self.error.append(f'{self.name} 存在悬空点')
            elif self.name.startswith('U'):
                if 1 in self.nc_points and 7 in self.nc_points:
                    self.error.append(f'放大器-{self.name} OUT端都为悬空')
                if 2 in self.nc_points and 6 in self.nc_points:
                    self.error.append(f'放大器-{self.name} IN-端都为悬空')
                if 3 in self.nc_points and 5 in self.nc_points:
                    self.error.append(f'放大器-{self.name} IN+端都为悬空')
            
# 电路
class Circuit(BaseModel):
    node_map: dict[str, Node] = {}
    component_map: dict[str, Component] = {}
    # 全局定义, 型号展示
    modelst: set[str] = set()
    # 子电路
    sub_circuits: dict[str, set['Circuit']] = {}
    # 开关控制
    # switch_control: dict[str, bool] = {}
    
    class Config:
        arbitrary_types_allowed = True
    
    def _link(self, name, point, info, node:Node, mark=None):
        if not self.component_map.get(name): self.component_map[name] = Component(name, info)
        component = self.component_map[name]
        # 悬空节点进行记录,不保存关系
        if node:
            component.point[point] = node
            component.p_mark[point] = mark or str(point)
            # 接地点不记录连接
            if not node.name.endswith('GND'):
                node.link_points.append(LinkPoint(component=component, point=point))
        else:
            component.nc_points.append(point)
            
    # 通过配置文件初始化电路
    def init(self, *path)-> list[list, list]:
        netmap = getNet(*path)
        infomap = getInfoMap(*path)
        # 默认有个接地节点
        gnd = Node(name='GND')
        self.node_map['GND'] = gnd
        for node_name, name_pms in netmap.items():    
            if node_name!='NC':
                node = Node(name=node_name)
                self.node_map[node_name] = node
            else:
                node = None
            # 判断节点是否为电源端
            if re.match(r'(\+|\-)\d+V', node_name):
                name = f'V{node_name}'
                self._link(name, 1, Info(value=node_name), node)
                self._link(name, 2, Info(value=node_name), gnd)
            # 连接元器件
            [self._link(name, point, Info(**infomap.get(name,{})), node, mark) for name, point, mark in name_pms]
        # 加载model,警告,错误
        self.modelst = set()
        warns,errs = [],[]
        for component in self.component_map.values():
            component.link_complate()
            component.verify()
            if component.model: self.modelst.add(component.model)
            if component.waring: warns.extend(component.waring)
            if component.error: errs.extend(component.error)
            # # 开关默认断开状态
            # if component.is_swicth: 
            #     # 修改控制端接入仿真节点, 如果已经存在控制信号则不再加入控制源
            #     if component.point.get(3): continue
            #     self.switch_control[component.name] = False
            #     component.point[3] = Node(name=f'swicth_{component.name}')
                
        # self._set_sub()
        # 设置易读名称
        for node in self.node_map.values():
            if re.match(r'N\d+$',node.name):
                node.symbol = ':'.join(f'{lp.point}{lp.component.name}' for lp in node.link_points)
                # node.symbol = f'[{node.symbol}]'
            elif node.name.endswith('GND'):
                node.symbol = '0'
            else:
                node.symbol = node.name
            # 处理+-符号
            node.symbol = node.symbol.replace('+','P').replace('-','M')
        # 抛出警告和错误
        return warns,errs

    def __str__(self):
        model = '\n'.join(model for model in self.modelst)
        component = '\n'.join(str(component) for name, component in self.component_map.items())
        # swicth = '\n'.join(f'V{name}_swicth swicth_{name} 0 DC 0V' for name, b in self.switch_control.items())
        with open('template/script.tp', 'r', encoding='utf-8') as f:
            return f.read().format(model=model, component=component)
         
    